(function () {
    const PART_SIZE = 8 * 1024 * 1024; // 8M
    const RETRY = 3;

    class FileUploader {
        constructor(file, onPartUploaded, onSuccess, onError) {
            this.file = file;
            this.partCount = Math.ceil(file.size / PART_SIZE);
            this.uploadId = "";
            this.uploadUrls = [];
            this.eTags = [];
            this.currentPart = 0;
            this.retryCount = 0;
            this.callback = {
                "onSuccess": () => { if (typeof (onSuccess) == "function") onSuccess() },
                "onError": (e, msg) => {
                    console.log(e);
                    if (typeof (onError) == "function") onError(msg)
                },
                "onPartUploaded": (current, total) => { if (typeof (onPartUploaded) == "function") onPartUploaded(current, total) }
            }
        }

        start() {
            const formData = new FormData();
            formData.append("fileName", this.file.name);
            formData.append("partCount", this.partCount);


            fetch("/xstore-s3-demo/s3/initUpload", {
                method: "POST",
                body: formData
            }).then(response => response.json()).then(res => {
                console.log(res);
                this.uploadId = res.uploadId;
                this.uploadUrls = res.uploadUrls;
                this.uploadNextPart();
            }).catch(e => {
                this.callback.onError(e, "初始化上传失败。")
            });
        }

        uploadNextPart() {
            if (!this.uploadUrls.length) {
                console.log("分片已经全部上传完成！");
                this.complete();
                return;
            }
            console.log(`正在上传分片${this.currentPart}`)
            const nextPart = this.currentPart + 1;
            const partBlob = this.file.slice(this.currentPart * PART_SIZE, nextPart * PART_SIZE);
            if (partBlob.size == 0) {
                console.log(`分片${this.currentPart}已经无法取得数据，返回！`)
                return;
            }
            const url = this.uploadUrls.shift();
            console.log(`Put part[${this.currentPart}] to url: ${url}`);
            fetch(url, {
                method: "PUT",
                headers: {
                    'Content-Type': "application/octet-stream"
                },
                body: partBlob
            }).then(res => {
                if (res.status != 200) {
                    this.retry(url);
                    return;
                }

                this.retry = 0;
                const eTag = res.headers.get("ETag");
                if (!eTag) {
                    this.callback.onError("", "无法从 headers 中取得 ETag！");
                    return;
                }

                this.eTags.push({
                    // ETags 的 partNumber 是从 1 开始的，因此，而我们的 currentPart 是 0 开始的。
                    "partNumber": nextPart,
                    "eTag": eTag
                });
                // 演示也是从 1 开始的。
                this.callback.onPartUploaded(nextPart, this.partCount);
                this.currentPart = nextPart;
                this.uploadNextPart();
            }).catch(e => {
                console.log(`异常发生：${e}`);
                this.retry(url);
            });
        }

        retry(url) {
            this.retryCount += 1;
            if (this.retryCount >= RETRY) {
                this.callback.onError(e, `上传分片${this.currentPart}失败！`)
                this.abort();
            } else {
                this.uploadUrls.unshift(url);
                this.uploadNextPart();
            }
        }

        complete() {
            const formData = new FormData();
            formData.append("fileName", this.file.name);
            formData.append("partCount", this.partCount);
            formData.append("uploadId", this.uploadId);
            console.log(this.eTags);
            for (let etag of this.eTags) {
                formData.append(`eTag-${etag.partNumber}`, etag.eTag);
            }
            fetch("/xstore-s3-demo/s3/completeUpload", {
                method: "POST",
                body: formData
            }).then(res => {
                if (res.status == 200) {
                    this.callback.onSuccess()
                } else {
                    this.callback.onError(res, "合并分片失败！");
                }
            }).catch(e => {

                this.callback.onError(e, "合并分片失败！");
                this.abort();
            });
        }

        abort() {
            const formData = new FormData();
            formData.append("fileName", this.file.name);
            formData.append("uploadId", this.uploadId);
            fetch("/xstore-s3-demo/s3/abortUpload", {
                method: "POST",
                body: formData
            }).then(res => {
                if (res.status != 200) {
                    this.callback.onError(e, "终止上传失败。")
                }
            }).catch(e => {
                this.callback.onError(e, "终止上传失败。")
            })
        }

    }

    function uploadFile() {
        const fileInput = document.getElementById("fileInput");
        if (!fileInput.files) {
            this.callback.onError(e, "请选择上传文件。");
        }
        const file = fileInput.files[0];

        const fileUploader = new FileUploader(file,
            (currentPart, total) => document.getElementById("progress-text").innerHTML = `${currentPart}/${total}`,
            () => console.log("上传文件成功！"),
            msg => alert(msg)
        );

        fileUploader.start();
    }

    document.getElementById("uploadBtn").addEventListener("click", uploadFile)
})();